﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Red.Controls;
using Red.Controls.Shell;
using Red.Controls.ViewModels;
using Red.Core;
using Red.EngineData.Protocol.Interop;
using Red.EngineData.View;

namespace Red.Toolkit.Console
{
    public static class ConsoleCommands
    {
        public static RedCommand ShowPreview = new RedCommand( "Show editor's preview", "appbar_monitor" );
    }

    [Export( typeof( IEditorInfo ) )]
    public class EditorInfo : IEditorInfo
    {
        public string Name
        {
            get { return "Red Toolkit Console"; }
        }

        public Type WindowClass
        {
            get { return typeof( Console ); }
        }

        public Type SettingsClass
        {
            get { return typeof( UserSettings ); }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public partial class Console : RedEditorWindow
    {
        #region Data

        private const string _welcomeStr =
            ">>    WELCOME TO RED TOOLKIT CONSOLE   <<\n" +
            ">> Abandon all hope, ye who enter here <<\n";
        private string _curPath = string.Empty;
        private InteropDataItem _rootEditorItem;
        private readonly AsyncLock _rootViewLock = new AsyncLock();

        #endregion Data

        #region C-tor

        public Console( IEditorHost host, EditorInstanceData instanceData, EditorSharedData sharedData )
            : base( host, instanceData, sharedData )
        {
            InitializeComponent();

            Term.TextWrapping = TextWrapping.Wrap;
            Term.LineEntered += Terminal_LineEntered;
            ResetPrompt();

            Breadcrumb.Separator = ".";
            Breadcrumb.EnumerateBranches += Breadcrumb_EnumerateBranches;
            Breadcrumb.NavigateTo += Breadcrumb_NavigateTo;
            RegisterCommands();
            ResetTerminal();

            BackendInitialized += Console_BackendInitialized;
        }

        #endregion C-tor

        #region Private methods

        private async Task<InteropDataItem> GetViewForPath( string path )
        {
            InteropDataItem root = await SharedData.EngineObjectRoot;
            InteropDataItem item = string.IsNullOrEmpty( path ) ? root : (InteropDataItem)await root.DigUpSubItemAsync( path );
            return item;
        }

        private async Task<List<string>> ListChildren( string path )
        {
            InteropDataItem curItem = await GetViewForPath( path );
            return new List<string>( curItem.Data.Children );
        }

        private string GetDisplayPath( string path )
        {
            return path.Length > 0 ? "/" + path : "/";
        }

        private string GetBreadcrumbPath( string path )
        {
            var res = "{,,icon:appbar_database}." + path.Replace( "[", ".[" );
            return res;
        }

        private string FixBreadcrumbItemPath( string path )
        {
            if ( path.StartsWith( "." ) )
                path = path.Substring( 1 );
            return path.Replace( ".[", "[" );
        }

        private void ResetPrompt()
        {
            Term.Prompt = $"[{GetDisplayPath( _curPath )}] > ";
        }

        private string FormatMetadata( InteropResult result )
        {
            string res = string.Empty;
            switch ( result.InteropType )
            {
                case EInteropType.Array:
                    res += "[array]";
                    break;
                case EInteropType.Functor:
                    res += "[functor]";
                    break;
                case EInteropType.Compound:
                    res += "[compound]";
                    break;
                case EInteropType.Value:
                    if ( string.IsNullOrEmpty( result.Type ) == false )
                        res += "[" + result.Type + "]";
                    break;
            }

            if ( result.IsReadOnly )
                res += " [r] ";

            return res;
        }

        private void DisplayResult( InteropResult result )
        {
            if ( string.IsNullOrEmpty( result.Data ) == false )
            {
                var metaStr = FormatMetadata( result );
                Term.AddLine( $"{result.Data} {metaStr}" );
            }

            if ( result.InteropType == EInteropType.Array || result.InteropType == EInteropType.Compound )
            {
                foreach ( string child in result.Children )
                {
                    Term.AddLine( child );
                }
            }

            Term.ShowPrompt();
        }

        private void DisplayException( Exception ex )
        {
            Term.AddLine( "ERROR: " + ex.Message );
            Term.ShowPrompt();
        }

        private void ResetTerminal()
        {
            Term.Clear();
            Term.AddLine( _welcomeStr );
            ShowCommandsHelp();
        }

        private async Task FeedToGrid( string path )
        {
            _dataCategory.DataContext = await GetViewForPath( _curPath );
        }

        #endregion Private methods

        #region Implementation

        protected override void WriteToSettings( RedSettings settings )
        {
            var userSettings = (UserSettings)settings;
            userSettings.CommandHistory = Term.SaveHistory( 100 );
        }

        protected override void LoadFromSettings( RedSettings settings )
        {
            var userSettings = (UserSettings)settings;
            if ( userSettings.CommandHistory != null )
            {
                Term.LoadHistory( userSettings.CommandHistory );
            }
        }

        protected override IntPtr? GetPreviewHandle()
        {
            return _preview.Handle;
        }

        #endregion Implementation

        #region Event handlers

        private async void Console_BackendInitialized( object sender, EventArgs e )
        {
            Term.Focus();
            Breadcrumb.Path = GetBreadcrumbPath( _curPath );

            string startingPath;
            if ( InstanceData.AdditionalParams.TryGetValue( "root", out startingPath ) )
            { // root editor object is given via parameter
                _rootEditorItem = await GetViewForPath( startingPath );
            }
            else if ( InstanceData.RootEditorItem != null )
            { // console is launched as an resource editor
                _rootEditorItem = InstanceData.RootEditorItem;
                startingPath = _rootEditorItem.Data.Path;
                _previewPanel.IsListed = true;
            }

            if ( !string.IsNullOrEmpty( startingPath ) )
            {
                Term.RunCommand( $"{_cmdPrefix}r /{startingPath}" );
            }
        }

        private async void Terminal_LineEntered( TerminalBox sender, string line )
        {
            if ( line.StartsWith( _cmdPrefix ) )
            {
                string[] cmd = line.Substring( 1 ).Split( new[] { ' ' } );
                CommandEntry cmdEntry;
                if ( _commands.TryGetValue( cmd[0], out cmdEntry ) )
                {
                    InteropDataItem root = await SharedData.EngineObjectRoot;
                    if ( root != null )
                    {
                        string[] args = new string[cmd.Length - 1];
                        if ( cmd.Length > 1 ) Array.Copy( cmd, 1, args, 0, cmd.Length - 1 );
                        cmdEntry.handler( root, args );
                    }
                    else
                    {
                        Term.AddLine( "ERROR: Cannot establish connection with Engine" );
                        Term.ShowPrompt();
                    }
                }
                else
                {
                    Term.AddLine( "ERROR: unknown console command" );
                    Term.ShowPrompt();
                }
            }
            else if ( line.Length > 0 )
            { // just pass the raw line to the Interop
                InteropDataItem root = await SharedData.EngineObjectRoot;
                string path = DataView.CombinePath( _curPath, line );

                root.Data.Execute( Interop.Make( path ),
                    result =>
                    {
                        DisplayResult( result );
                    },
                    ex =>
                    {
                        DisplayException( ex );
                    } );
            }
            else
            {
                Term.ShowPrompt();
            }
        }

        private Task<List<string>> Breadcrumb_EnumerateBranches( BreadcrumbBarItem e )
        {
            string path = FixBreadcrumbItemPath( e.Path );
            // NOTE: branches are enumerated by creating DataViews, which is not strictly necessary (we may just use Interop all the way)
            // but it's done this way to have an ongoing "test" of the EngineData level.
            return ListChildren( path );
        }

        private void Breadcrumb_NavigateTo( object sender, BreadcrumbBarItem e )
        {
            string path = FixBreadcrumbItemPath( e.Path );
            // navigate by invoking a command - this way it's clear what's happening and it's stored in the history
            Term.RunCommand( $"{_cmdPrefix}r /{path}" );
        }

        #endregion Event handlers
    }
}
